﻿// -----------------------------------------------------------------------
// <copyright file="MetalList.cs" company="Abyssalsoft">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace Gui.Controls
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows.Forms;
    using System.Data;
    using System.IO;
    using System.Drawing;

    /// <summary>
    /// Customized DataGridView for Metal Player
    /// </summary>
    public class MetalList : DataGridView
    {
        #region Members

        // Contains the data set that is binded to control
        private DataSet listDataSet;
        private DataView listDataView;

        private int oldRowIndex = 0;

        // Fore color (default)
        public Color CustomRowForegroundColor;
        public Color CustomSecondRowForegroundColor;
        
        // Fore color (selected row)
        public Color CustomRowSelectedForegroundColor;
        public Color CustomSecondRowSelectedForegroundColor;

        // Background color (selected row & gradient)
        public Color CustomRowSelectedBackColor;
        public Color CustomRowGradientSecondColor;

        // Reorder with drag and drop
        private Rectangle dragBoxFromMouseDown;
        private int rowIndexFromMouseDown;
        private int rowIndexOfItemUnderMouseToDrop;

        private string commandIdentifier;

        private bool mouseDown;
        private int SelectedIndexWithDoubleClick;
        private bool showSelectedRow;
        private bool[] visibleColumns;

        private ListStyle style;
        private Image backgroundImage;
        private Image temporaryBackground;

        /// <summary> Our font object </summary>
        private System.Drawing.Text.PrivateFontCollection pfc;

        private int thumbSize;
        private int rowHeight;

        private bool hideBackgroundOnScroll;

        #endregion

        #region Constructor

        public MetalList()
        {
            this.AllowDrop = true;

            // Make the columns to fill all the grid (auto resizable)
            this.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;

            // AllowUserToAddRows must be set to false, or else a damn empty row will show at the bottom of the list.
            this.AllowUserToAddRows = false;
            this.EditMode = DataGridViewEditMode.EditOnKeystrokeOrF2;
            this.CellBorderStyle = DataGridViewCellBorderStyle.None;
            this.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            
            // Set headers default values
            this.EnableHeadersVisualStyles = false;
            this.ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.None;

            // Adjust the row heights to accommodate the normal cell content.
            this.AutoResizeRows(DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders);
            
            this.SubscribeToEvents();

            // Set the value of the double-buffering style bits to true.
            this.SetStyle(System.Windows.Forms.ControlStyles.AllPaintingInWmPaint | System.Windows.Forms.ControlStyles.UserPaint | System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer, true);
        }

        #endregion

        #region Enums

        public enum ListStyle
        {
            Simple,
            Details
        }

        #endregion

        #region List properties

        public DataSet ListDataSet
        {
            get
            {
                return this.listDataSet;
            }
            set
            {
                if (value.Tables.Count > 0)
                {
                    this.DataSource = value.Tables[0];

                    this.Columns[0].Width = 30;
                    this.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.None;

                    for (int val = 0; val < value.Tables[0].Columns.Count; val++)
                    {
                        this.Columns[val].Visible = VisibleColumns[val];
                    }

                    this.ListDataView = ListDataSet.Tables[0].DefaultView;
                    this.ListDataView.Sort = listDataSet.Tables[0].Columns[0].ToString();
                }

                this.HideScrollThumbIfNotNeeded();
                this.listDataSet = value;
            }
        }

        public DataView ListDataView
        {
            get
            {
                return this.listDataView;
            }
            set
            {
                this.listDataView = value;
            }
        }

        public bool[] VisibleColumns
        {
            get
            {
                return this.visibleColumns;
            }
            set
            {
                this.visibleColumns = value;
            }
        }

        public string CommandIdentifier
        {
            get
            {
                return this.commandIdentifier;
            }
            set
            {
                this.commandIdentifier = value;
            }
        }

        public bool ShowSelectedRow
        {
            get
            {
                return this.showSelectedRow;
            }

            set
            {
                this.showSelectedRow = value;
            }
        }

        public override Image BackgroundImage
        {
            get { return backgroundImage; }
            set { backgroundImage = value; }
        }

        public ListStyle Style
        {
            get
            {
                return this.style;
            }
            set
            {
                if (value == ListStyle.Details)
                {
                    // Set a cell padding to provide space for the top of the focus rectangle and for the content that spans multiple columns.
                    Padding newPadding = new Padding(0, 0, 0, this.RowHeight);
                    this.RowTemplate.DefaultCellStyle.Padding = newPadding;

                    this.RowTemplate.Height = this.RowHeight;

                    // Set the row height to accommodate the content that spans multiple columns.
                    this.RowTemplate.Height += this.RowHeight;
                }
                else
                {
                    // Set a cell padding to provide space for the top of the focus rectangle and for the content that spans multiple columns.
                    Padding newPadding = new Padding(0, 0, 0, -8);
                    this.RowTemplate.DefaultCellStyle.Padding = newPadding;
                }

                // Set the selection background color to transparent so the cell won't paint over the custom selection background.
                this.RowTemplate.DefaultCellStyle.SelectionBackColor = Color.Transparent;
                this.RowTemplate.DefaultCellStyle.BackColor = Color.Transparent;

                this.style = value;
            }
        }

        public int RowHeight
        {
            get
            {
                return this.rowHeight;
            }
            set
            {
                this.rowHeight = value;

                Padding newPadding = new Padding(0, 0, 0, this.RowHeight);
                this.RowTemplate.DefaultCellStyle.Padding = newPadding;
                this.RowTemplate.Height = this.RowHeight;
                this.RowTemplate.Height += this.RowHeight;
            }
        }

        public bool HideBackgroundOnScroll
        {
            get
            {
                return this.hideBackgroundOnScroll;
            }
            set
            {
                this.hideBackgroundOnScroll = value;
            }
        }

        #endregion

        #region Public methods

        public void SetFont(string fontName, int fontSize, System.Drawing.FontStyle fontStyle)
        {
            if (fontName == "Default")
            {
                return;
            }

            // Load font from file
            this.pfc = new System.Drawing.Text.PrivateFontCollection();

            this.pfc.AddFontFile(Metal.Config.Paths.ThemesFolder + Metal.Config.Settings.Instance.GetValue("LastSettings.Theme", "BlackMetal") + "\\" + fontName + ".ttf");

            // Apply font
            this.Font = new System.Drawing.Font(this.pfc.Families[0], fontSize, fontStyle);
        }

        #endregion

        #region Private methods

        // Attach handlers to DataGridView events.
        private void SubscribeToEvents()
        {
            this.DragEnter += new DragEventHandler(OnDragEnter);
            this.RowsAdded += new DataGridViewRowsAddedEventHandler(this.OnRowAdded);

            // If the skin has some customized scrollbar, the default scrollbar is not visible and the wheel will do nothing.
            // In that case, we should subscribe to the mouse wheel event and to that job. :)
            this.MouseWheel += new MouseEventHandler(OnMouseWheel);

            this.DataSourceChanged += new System.EventHandler(this.OnDataSourceChanged);
            this.ControlAdded += new System.Windows.Forms.ControlEventHandler(this.OnControlAdded);

            // This events are used to customize the layout
            this.ColumnWidthChanged += new DataGridViewColumnEventHandler(OnColumnWidthChanged);
            this.RowPrePaint += new DataGridViewRowPrePaintEventHandler(OnRowPrePaint);
            this.RowPostPaint += new DataGridViewRowPostPaintEventHandler(OnRowPostPaint);
            this.CurrentCellChanged += new EventHandler(OnCurrentCellChanged);
            this.RowHeightChanged += new DataGridViewRowEventHandler(OnRowHeightChanged);

            // This events are used for reordering with drag and drop
            this.MouseMove += new MouseEventHandler(MetalList_MouseMove);
            this.MouseDown += new MouseEventHandler(MetalList_MouseDown);
            this.MouseUp += new MouseEventHandler(MetalList_MouseUp);
            this.DragOver += new DragEventHandler(MetalList_DragOver);
            this.DragDrop += new DragEventHandler(MetalList_DragDrop);

            this.DoubleClick += new EventHandler(MetalList_DoubleClick);
        }

        private void HideScrollThumbIfNotNeeded()
        {
            if (this.Controls["Scrollbar"] != null)
            {
                (this.Controls["Scrollbar"] as Controls.MetalSlider).Value = this.FirstDisplayedScrollingRowIndex;

                // Hide thumb if the scrollbar is not needed
                if (this.DisplayedRowCount(true) < 12)
                {
                    (this.Controls["Scrollbar"] as Controls.MetalSlider).ThumbSize = 1;
                    (this.Controls["Scrollbar"] as Controls.MetalSlider).MouseEffects = false;
                }
                else
                {
                    (this.Controls["Scrollbar"] as Controls.MetalSlider).ThumbSize = this.thumbSize;
                    (this.Controls["Scrollbar"] as Controls.MetalSlider).MouseEffects = true;
                }
            }
        }

        #endregion

        #region Overrided properties

        protected override void PaintBackground(Graphics graphics, Rectangle clipBounds, Rectangle gridBounds)
        {
            base.PaintBackground(graphics, clipBounds, gridBounds);
            if (backgroundImage != null)
            {
                graphics.DrawImage(backgroundImage, 0, 0, gridBounds.Width, gridBounds.Height);
            }
        }

        protected override void OnScroll(ScrollEventArgs e)
        {
            if (this.Controls["Scrollbar"] != null)
            {
                (this.Controls["Scrollbar"] as Controls.MetalSlider).Value = this.FirstDisplayedScrollingRowIndex;
            }

            base.OnScroll(e);
            Invalidate();
        }

        #endregion

        # region List events

        private void MetalList_DoubleClick(object sender, EventArgs e)
        {
            try
            {
                if (!ShowSelectedRow)
                {
                    return;
                }

                if (SelectedIndexWithDoubleClick < this.Rows.Count)
                {
                    // Reset previous row fore color
                    this.Rows[SelectedIndexWithDoubleClick].DefaultCellStyle.ForeColor = CustomRowForegroundColor;

                    // Change fore color for selected file (row)
                    SelectedIndexWithDoubleClick = this.SelectedRows[0].Index;
                    this.SelectedRows[0].DefaultCellStyle.ForeColor = CustomRowSelectedForegroundColor;
                }
            }
            catch(Exception ex)
            {
                throw ex;
            }
        }

        private void MetalList_MouseUp(object sender, MouseEventArgs e)
        {
            mouseDown = false;
        }

        private void MetalList_MouseMove(object sender, MouseEventArgs e)
        {
            if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
            {
                // If the mouse moves outside the rectangle, start the drag.
                if (dragBoxFromMouseDown != Rectangle.Empty && !dragBoxFromMouseDown.Contains(e.X, e.Y))
                {
                    // Proceed with the drag and drop, passing in the list item.                    
                    DragDropEffects dropEffect = this.DoDragDrop(this.Rows[rowIndexFromMouseDown], DragDropEffects.Move);
                }
            }
        }

        private void MetalList_MouseDown(object sender, MouseEventArgs e)
        {
            var hti = this.HitTest(e.X, e.Y);

            // Store this row (maybe the user want's to drop it in another list - the row it will be added on DragDrop event)
            if (hti.RowIndex != -1)
            {
                StaticRowTransfer.TransferRow = this.Rows[hti.RowIndex];
                StaticRowTransfer.ListSourceName = this.CommandIdentifier;
            }

            if (e.Button == MouseButtons.Right && hti.RowIndex != -1)
            {    
                this.ClearSelection();
                this.Rows[hti.RowIndex].Selected = true;
            }

            mouseDown = true;

            // Get the index of the item the mouse is below.
            rowIndexFromMouseDown = this.HitTest(e.X, e.Y).RowIndex;
            if (rowIndexFromMouseDown != -1)
            {
                // Remember the point where the mouse down occurred. The DragSize indicates the size that the mouse can move before a drag event should be started.                
                Size dragSize = SystemInformation.DragSize;

                // Create a rectangle using the DragSize, with the mouse position being at the center of the rectangle.
                dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2), e.Y - (dragSize.Height / 2)), dragSize);
            }
            else
            {
                // Reset the rectangle if the mouse is not over an item in the ListBox.
                dragBoxFromMouseDown = Rectangle.Empty;
            }
        }

        private void MetalList_DragOver(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Move;
        }

        private void MetalList_DragDrop(object sender, DragEventArgs e)
        {
            try
            {
                // Check if user dropped a row from another list
                if (StaticRowTransfer.TransferRow != null && StaticRowTransfer.ListSourceName != this.CommandIdentifier)
                {
                    this.listDataView.Table.Rows.Add(ListDataSet.Tables[0].Rows.Count + 3, StaticRowTransfer.TransferRow.Cells[1].Value, StaticRowTransfer.TransferRow.Cells[2].Value);
                    StaticRowTransfer.TransferRow = null;
                    return;
                }

                if (!mouseDown)
                {
                    return;
                }
                mouseDown = false;

                // The mouse locations are relative to the screen, so they must be converted to client coordinates.
                Point clientPoint = this.PointToClient(new Point(e.X, e.Y));

                // Get the row index of the item the mouse is below. 
                rowIndexOfItemUnderMouseToDrop = this.HitTest(clientPoint.X, clientPoint.Y).RowIndex;

                if (e.Effect == DragDropEffects.Move)
                {
                    DataGridViewRow rowToMove = e.Data.GetData(typeof(DataGridViewRow)) as DataGridViewRow;

                    if (rowToMove.Index != -1)
                    {
                        // Start from 1 (ignore first column - the index - will not be moved)
                        for (int i = 1; i < this.Columns.Count; i++)
                        {
                            string strMoveFrom = this.ListDataView.Table.Rows[rowIndexFromMouseDown].ItemArray[i].ToString();
                            string strMoveTo = this.ListDataView.Table.Rows[rowIndexOfItemUnderMouseToDrop].ItemArray[i].ToString();
                            this.ListDataView.Table.Rows[rowIndexOfItemUnderMouseToDrop][i] = strMoveFrom;
                            this.ListDataView.Table.Rows[rowIndexFromMouseDown][i] = strMoveTo;
                        }
                    }

                    this.Rows[rowIndexFromMouseDown].Selected = false;
                    this.Rows[rowIndexOfItemUnderMouseToDrop].Selected = true;
                }
            }
            catch
            {
            }
        }

        private void OnRowAdded(object sender, DataGridViewRowsAddedEventArgs e)
        {
            if (this.Controls["Scrollbar"] != null)
            {
                (this.Controls["Scrollbar"] as Controls.MetalSlider).Maximum = this.RowCount;
            }
        }

        /// <summary>
        /// Make drag and drop efect.
        /// </summary>
        /// <param name="sender">Object sender</param>
        /// <param name="e">Event arguments</param>
        private void OnDragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Copy;
            }
        }

        /// <summary>
        /// Makes the list scroll on mouse wheel.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnMouseWheel(object sender, MouseEventArgs e)
        {
            try
            {
                if (e.Delta > 0 && this.FirstDisplayedScrollingRowIndex > 0)
                {
                    this.FirstDisplayedScrollingRowIndex--;
                }
                else if (e.Delta < 0)
                {
                    this.FirstDisplayedScrollingRowIndex++;
                }

                if (this.Controls["Scrollbar"] != null)
                {
                    (this.Controls["Scrollbar"] as Controls.MetalSlider).Value = this.FirstDisplayedScrollingRowIndex;
                }
            }
            catch
            {
            }
        }

        /// <summary>
        /// Scroll the list up or down when the user drags the scrollbar.
        /// </summary>
        /// <param name="sender">Object sender</param>
        /// <param name="e">Event arguments</param>
        private void OnScrollbarValueChanged(object sender, EventArgs e)
        {
            int newValue = (sender as Controls.MetalSlider).Value;
            if (newValue < this.RowCount)
            {
                this.FirstDisplayedScrollingRowIndex = (sender as Controls.MetalSlider).Value;
                this.Focus();
            }
           this.Invalidate();
        }

        /// <summary>
        /// This method is called when the data source is changed. This updates the scrollbar maximum value.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnDataSourceChanged(object sender, EventArgs e)
        {
            this.SubscribeToEvents();

            if (this.Controls["Scrollbar"] != null)
            {
                (this.Controls["Scrollbar"] as Controls.MetalSlider).Maximum = this.RowCount;
            }
        }

        /// <summary>
        /// This method is called when a new control is added to the list.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnControlAdded(object sender, System.Windows.Forms.ControlEventArgs e)
        {
            // Subscribe to Scrollbar ValueChanged event. We need it to scroll the list when the user drags the scroll.
            if (e.Control.GetType() == typeof(Controls.MetalSlider))
            {
                (e.Control as Controls.MetalSlider).ValueChanged += new EventHandler(this.OnScrollbarValueChanged);
                (e.Control as Controls.MetalSlider).MouseDown +=new MouseEventHandler(this.OnScrollbarMouseDown);
                (e.Control as Controls.MetalSlider).MouseUp += new MouseEventHandler(this.OnScrollbarMouseUp);
                this.thumbSize = (e.Control as Controls.MetalSlider).ThumbSize;
            }
        }

        private void OnScrollbarMouseDown(object sender, MouseEventArgs e)
        {
            if (HideBackgroundOnScroll)
            {
                temporaryBackground = this.BackgroundImage;
                this.BackgroundImage = null;
            }
        }

        private void OnScrollbarMouseUp(object sender, MouseEventArgs e)
        {
            if (HideBackgroundOnScroll && this.temporaryBackground != null)
            {
                this.BackgroundImage = this.temporaryBackground;
            }
        }

        // Adjusts the padding when the user changes the row height so that the normal cell content is fully displayed and any extra
        // height is used for the content that spans multiple columns.
        private void OnRowHeightChanged(object sender, DataGridViewRowEventArgs e)
        {
            // Calculate the new height of the normal cell content.
            int preferredNormalContentHeight = e.Row.GetPreferredHeight(e.Row.Index, DataGridViewAutoSizeRowMode.AllCellsExceptHeader, true) -
            e.Row.DefaultCellStyle.Padding.Bottom;

            // Specify a new padding.
            Padding newPadding = e.Row.DefaultCellStyle.Padding;
            newPadding.Bottom = e.Row.Height - preferredNormalContentHeight;
            e.Row.DefaultCellStyle.Padding = newPadding;
        }

        // Paints the content that spans multiple columns and the focus rectangle.
        private void OnRowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
        {
            // Calculate the bounds of the row.
            Rectangle rowBounds = new Rectangle(0, e.RowBounds.Top, this.Columns.GetColumnsWidth(DataGridViewElementStates.Visible) -
            this.HorizontalScrollingOffset + 1, e.RowBounds.Height);

            SolidBrush forebrush = null;
            SolidBrush forebrushSecondRow = null;

            try
            {
                // Determine the foreground color.
                if ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected)
                {
                    forebrushSecondRow = new SolidBrush(CustomSecondRowSelectedForegroundColor);
                }
                else
                {
                    forebrushSecondRow = new SolidBrush(CustomSecondRowForegroundColor);
                }

                // Get the content that spans multiple columns.
                object recipe = null;

                recipe = this.Rows[e.RowIndex].Cells[1].Value;
                //if (this.Rows.SharedRow(e.RowIndex).Index != -1)
                    //recipe = this.Rows.SharedRow(e.RowIndex).Cells[0].Value;

                if (recipe != null)
                {
                    string text = recipe.ToString();

                    // Calculate the bounds for the content that spans multiple columns, adjusting for the horizontal scrolling position 
                    // and the current row height, and displaying only whole lines of text.
                    Rectangle textArea = rowBounds;
                    textArea.X -= this.HorizontalScrollingOffset;
                    textArea.Width += this.HorizontalScrollingOffset;
                    textArea.Y += rowBounds.Height - e.InheritedRowStyle.Padding.Bottom;
                    textArea.Height -= rowBounds.Height - e.InheritedRowStyle.Padding.Bottom;
                    textArea.Height = (textArea.Height / e.InheritedRowStyle.Font.Height) * e.InheritedRowStyle.Font.Height;

                    // Calculate the portion of the text area that needs painting.
                    RectangleF clip = textArea;
                    clip.Width -= 1 - clip.X;
                    clip.X = 0;
                    RectangleF oldClip = e.Graphics.ClipBounds;
                    e.Graphics.SetClip(clip);

                    // Draw the content that spans multiple columns.
                    e.Graphics.DrawString(text, e.InheritedRowStyle.Font, forebrushSecondRow, textArea);

                    e.Graphics.SetClip(oldClip);
                }
            }
            finally
            {
                if (forebrush != null)
                    forebrush.Dispose();
                if (forebrushSecondRow != null)
                    forebrushSecondRow.Dispose();
            }
        }

        // Paints the custom selection background for selected rows.
        private void OnRowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
        {
            // Do not automatically paint the focus rectangle.
            e.PaintParts &= ~DataGridViewPaintParts.Focus;

            // Determine whether the cell should be painted with the custom selection background.
            if ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected)
            {
                // Calculate the bounds of the row.
                Rectangle rowBounds = new Rectangle(0, e.RowBounds.Top,
                this.Columns.GetColumnsWidth(DataGridViewElementStates.Visible) - this.HorizontalScrollingOffset + 1, e.RowBounds.Height);

                // Paint the custom selection background.
                using (Brush backbrush = new System.Drawing.Drawing2D.LinearGradientBrush(rowBounds,
                CustomRowSelectedBackColor, CustomRowGradientSecondColor,
                System.Drawing.Drawing2D.LinearGradientMode.Horizontal))
                {
                    e.Graphics.FillRectangle(backbrush, rowBounds);
                }
            }
        }

        // Forces the row to repaint itself when the user changes the current cell. This is necessary to refresh the focus rectangle.
        private void OnCurrentCellChanged(object sender, EventArgs e)
        {
            if (oldRowIndex != -1)
            {
                this.InvalidateRow(oldRowIndex);
            }
            oldRowIndex = this.CurrentCellAddress.Y;
        }

        // Forces the control to repaint itself when the user manually changes the width of a column.
        private void OnColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
        {
            this.Invalidate();
        }

        # endregion
    }
}
